List & Label .NET
Einführung in die Programmierung / Weitere wichtige Konzepte / Multithreading und Schutzjob
In diesem Thema
    Multithreading und Schutzjob
    In diesem Thema

     

    List & Label kann in mehreren Threads gleichzeitig verwendet werden. Auf diese Weise können umfangreichere Druck-Aufgaben z.B. auf mehrere Prozessoren/Cores verteilt werden. Intern wird von diesen Möglichkeiten rege Gebrauch gemacht, z.B. bei der Druckvorschau im Designer oder beim DrillDown. Bei der Verwendung in Multithread-Umgebungen sind einige Punkte zu berücksichtigen.

     

    List & Label Objekt in Threads

    Achten Sie darauf, dass ein List & Label Job (bzw. eine Komponenteninstanz) immer nur innerhalb des gleichen Threads verwendet wird. Die Erzeugung, Verwendung und Zerstörung des Jobs / der Komponente muss also im gleichen Thread stattfinden. Wenn Sie mehrere parallele Druckthreads starten möchten, muss also jeder dieser Druckthreads seinen eigenen Job öffnen und schließen. Hintergrund: Windows-GDI-Ressourcen wie Fensterhandles, Drucker-Devicekontexte etc. können nicht aus verschiedenen Threads verwendet werden.

     

    Schutzjob

    Stellen Sie sicher, dass Sie vor dem Start des ersten Threads einen sogenannten Schutzjob öffnen (bzw. eine Komponenteninstanz erzeugen) und diesen erst nach dem Beenden des letzten Threads wieder schließen. Typischerweise wird Ihre Applikation beim Start diesen Job erzeugen und beim Beenden wieder zerstören. Hintergrund: der erste Job erzeugt einige Hilfsobjekte, die im gleichen Thread wieder zerstört werden müssen. Zudem kann die Performance auf diese Weise deutlich verbessert werden, da ein häufiges Laden und Entladen der List & Label DLLs vermieden wird.

    Hinweis: Soll List & Label unabhängig von Druckertreibern arbeiten (meist in Web-Anwendungen etc.) kann dafür der Printerless-Modus aktiviert werden. Die Eigenschaft Printerless muss dafür im ersten Objekt/Schutzjob gesetzt werden. Eine Mischung von aktiviertem und deaktiviertem Printerless-Modus in den in der Anwendung verwendeten Objekten wird nicht unterstützt und kann zu unerwartetem Verhalten führen.

     

    Thread-Modell

    Threads, die den Designer anzeigen, müssen das Single Threaded Apartment-Modell (STA) verwenden. Es dürfen also keine .NET Worker Threads aus dem Threadpool sein, da diese das Multi Threaded Apartment-Modell (MTA) verwenden. Hintergrund: für die Drag & Drop-Unterstützung im Designer muss OleInitialize() aufgerufen werden, was STA zwingend benötigt.

     

    Beispiel

    Das folgende Code-Snippet greift alle wichtigen Punkte von oben auf und simuliert dabei das Starten Reporting.StartApplication() und das Beenden Reporting.ExitApplication() einer Anwendung und dient lediglich der vereinfachten Veranschaulichung und muss auf die vorliegenden Gegebenheiten/Anwendung angepasst werden.

    Es zeigt ausführlich, wie der Schutzjob _protectionJob zu verwenden ist. Dabei zentral wichtig ist, dass immer eine neue/frische ListLabel-Instanz (Workerjob) verwendet wird für die zentralen Funktionen Design, Print oder Export - der Schutzjob wird lediglich im Speicher gehalten und wird für keinerlei Reporting-Funktionen aktiv verwendet.

    Darüber hinaus wird eine zentrale Routine CreateListLabel() verwendet, um benötigte ListLabel-Instanzen zu erzeugen und dabei immer auf zentrale Basis-Optionen zurückgegriffen wird wie bspw. die Eigenschaft LicensingInfo. Das erleichtert auch eine Aktualisierung bei einem Versionswechseln.

    ...
    
    // Verwendung/Simulation als Anwendung.
    
    
    // Anwendung wird gestartet - Schutzjob wird erstellt.
    Reporting myApp = new Reporting();
    myApp.StartApplication();
    
    // ...
    
    // Startet den Designer: Ein neuer Workerjob wird dafür erstellt und direkt freigegeben.
    myApp.DesignReport();
    
    // ...
    
    // Startet einen Export/Druck: Ein neuer Workerjob wird dafür erstellt und direkt freigegeben.
    myApp.AnyFunctionWithinTheApplication();
    
    // ...
    
    // Anwendung wird beendet - Schutzjob wird freigegeben.
    myApp.ExitApplication();
    
    ...
    
    // Beispiel für die Implementierung eines Grundgerüsts für die Verwendung von ListLabel-Instanzen.
    public class Reporting
    {
        // Globale ListLabel-Instanz (Schutzjob) für Modul-Ladevorgänge und Caching.
        // Diese Instanz wird *nicht* zum Designen/Exportieren verwendet.
        private ListLabel? _protectionJob;
    
        // Haupt-Routine zum Starten der Anwendung.
        public void StartApplication()
        {
            // Erstellt die globale Schutzjob-Instanz, um Module in den Speicher zu laden
            // und interne Caches aufzubauen. Dadurch starten spätere Reportvorgänge schneller.
            // Wichtig: Dieses Objekt wird nicht weiter aktiv verwendet im Verlauf der Anwendung.
            _protectionJob = helperCreateListLabel(isProtectionJob: true);
        }
    
        // Beispiel-Methode, die zeigt, wie ein Reporting-Vorgang
        // asynchron in einem eigenen Thread gestartet werden kann.
        public void AnyFunctionWithinTheApplication()
        {
            // Erstellt und Startet einen neuen Thread, der die Methode ExecuteReport ausführt.
            Thread newThread = new Thread(ExecuteReport);
            newThread.Start();
        }
    
        // Haupt-Routine zum Beenden der Anwendung.
        public void ExitApplication()
        {
            // Schutzjob wieder freigeben, damit Caches und /Module entladen werden.
            _protectionJob?.Dispose();
            _protectionJob = null;
        }
    
        // Führt einen Bericht als Export aus. 
        // Für jede Ausführung wird eine frische Workerjob-Instanz erzeugt,
        // damit laufende Jobs sich nicht gegenseitig beeinflussen.
        private void ExecuteReport()
        {
            // Workerjob-Instanz ist kurzlebig und kapselt alle job-spezifischen Einstellungen.
            using ListLabel worker = helperCreateListLabel(isProtectionJob: false);
    
            // Exportoptionen setzen, Datenquelle(n) zuweisen.
            // worker.ExportOptions.Add(LlExportOption.ExportTarget, "PDF");
            // worker.DataSource = myDataProvider;
            worker.DataSource = new List<string>() { "my values" };
    
            //worker.Export(...);
        }
    
        // Öffnet den Designer für einen Bericht.
        // Nutzt eine frische Workerjob-Instanz, damit globale Caches unberührt bleiben.
        public void DesignReport()
        {
            // Workerjob-Instanz ist kurzlebig und kapselt alle job-spezifischen Einstellungen.
            using ListLabel worker = helperCreateListLabel(isProtectionJob: false);
    
            // Datenquelle(n) zuweisen, diverse Einstellungen setzen etc.
            // worker.DataSource = myDataProvider;
            worker.DataSource = new List<string>() { "my values" };
    
            worker.Design();
        }
    
        // Erzeugt eine neue ListLabel-Instanz.
        // isProtectionJob
        // true: Instanz dient ausschließlich als Schutzjob (Module/Caching).
        // false: Instanz ist ein kurzlebiger Workerjob für Design/Print/Export.
        private static ListLabel helperCreateListLabel(bool isProtectionJob)
        {
            var ll = new ListLabel
            {
                // Gültige Lizenzinformationen eintragen - siehe auch redist.txt in der Installation.
                LicensingInfo = ""
            };
    
            if (!isProtectionJob)
            {
                // Hier Workerjob-spezifische Standardoptionen setzen.
                // z.B. ll.Language = LlLanguage.German;
            }
            return ll;
        }
    }
    ...